/**
 * Copyright (c) 2015-2017, Henry Yang 杨勇 (gismail@foxmail.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lambkit.component.shiro;

import com.jfinal.config.Routes;
import com.jfinal.core.ActionKey;
import com.jfinal.core.Controller;
import com.jfinal.kit.StrKit;
import com.jfinal.template.Directive;
import com.jfinal.template.Engine;
import com.lambkit.Lambkit;
import com.lambkit.common.util.ArrayUtils;
import com.lambkit.common.util.ClassUtils;
import com.lambkit.component.shiro.processer.*;
import com.lambkit.web.directive.annotation.JFinalDirective;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.*;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * shiro 管理器.
 */
public class ShiroManager {
	private static ShiroManager me = new ShiroManager();

	private ShiroManager() {
	}

	public static ShiroManager me() {
		return me;
	}

	private static final String SLASH = "/";
	private ConcurrentHashMap<String, ShiroAuthorizeProcesserInvoker> invokers = new ConcurrentHashMap<>();

	private ShiroRequiresAuthenticationProcesser requiresAuthenticationProcesser = new ShiroRequiresAuthenticationProcesser();
	private ShiroRequiresUserProcesser requiresUserProcesser = new ShiroRequiresUserProcesser();
	private ShiroRequiresGuestProcesser requiresGuestProcesser = new ShiroRequiresGuestProcesser();
	
	private LambkitShiroConfigService shiroConfigService; 

	public void init(Routes routes) {
		List<Routes.Route> list = new ArrayList<>();
		list.addAll(routes.getRouteItemList());
		for (Routes r : Routes.getRoutesList()) {
			list.addAll(r.getRouteItemList());
		}
		init(list);
	}

	public void init(List<Routes.Route> routes) {
		initInvokers(routes);
	}

	public void configEngine(Engine me) {
		// 4.shiro
		Set<Class<?>> controllerClassList = ClassUtils.scanPackageBySuper("com.lambkit.component.shiro.directives",
				true, Directive.class);
		if (controllerClassList == null) {
			return;
		}
		// 遍历Controller
		for (Class<?> clazz : controllerClassList) {
			// 提取JFinalDirective注解
			JFinalDirective jDirective = clazz.getAnnotation(JFinalDirective.class);
			if (jDirective != null) {
				me.removeDirective(jDirective.value());
				me.addDirective(jDirective.value(), (Class<? extends Directive>) clazz);
			}
		}
		/*
		 * List<Class> directiveClasses =
		 * ClassScanner.scanClassByAnnotation(JFinalDirective.class, false); for (Class
		 * clazz : directiveClasses) { JFinalDirective jDirective = (JFinalDirective)
		 * clazz.getAnnotation(JFinalDirective.class); if (jDirective != null) {
		 * Directive directive = AopKit.get((Class<Directive>) clazz); if (directive !=
		 * null) { me.removeDirective(jDirective.value());
		 * me.addDirective(jDirective.value(), directive); } } }
		 */
	}

	/**
	 * 初始化 invokers 变量
	 */
	private void initInvokers(List<Routes.Route> routes) {
		Set<String> excludedMethodName = buildExcludedMethodName();

		for (Routes.Route route : routes) {

			Class<? extends Controller> controllerClass = route.getControllerClass();

			String controllerKey = route.getControllerKey();
			// System.out.println("controllerKey:"+controllerKey);

			Annotation[] controllerAnnotations = controllerClass.getAnnotations();

			Method[] methods = controllerClass.getMethods();
			for (Method method : methods) {
				if (excludedMethodName.contains(method.getName()) || method.getParameterTypes().length != 0) {
					continue;
				}

				if (method.getAnnotation(ShiroClear.class) != null) {
					continue;
				}

				Annotation[] methodAnnotations = method.getAnnotations();
				Annotation[] allAnnotations = ArrayUtils.concat(controllerAnnotations, methodAnnotations);

				String actionKey = createActionKey(controllerClass, method, controllerKey);
				ShiroAuthorizeProcesserInvoker invoker = new ShiroAuthorizeProcesserInvoker();

				for (Annotation annotation : allAnnotations) {
					if (annotation.annotationType() == RequiresPermissions.class) {
						ShiroRequiresPermissionsProcesser processer = new ShiroRequiresPermissionsProcesser(
								(RequiresPermissions) annotation);
						invoker.addProcesser(processer);
					} else if (annotation.annotationType() == RequiresRoles.class) {
						ShiroRequiresRolesProcesser processer = new ShiroRequiresRolesProcesser(
								(RequiresRoles) annotation);
						invoker.addProcesser(processer);
					} else if (annotation.annotationType() == RequiresUser.class) {
						invoker.addProcesser(requiresUserProcesser);
					} else if (annotation.annotationType() == RequiresAuthentication.class) {
						invoker.addProcesser(requiresAuthenticationProcesser);
					} else if (annotation.annotationType() == RequiresGuest.class) {
						invoker.addProcesser(requiresGuestProcesser);
					}
				}

				if (invoker.getProcessers() != null && invoker.getProcessers().size() > 0) {
					invokers.put(actionKey, invoker);
				}

			}
		}
	}

	/**
	 * 参考ActionMapping中的实现。
	 *
	 * @param controllerClass
	 * @param method
	 * @param controllerKey
	 * @return
	 */

	private String createActionKey(Class<? extends Controller> controllerClass, Method method, String controllerKey) {
		String methodName = method.getName();
		String actionKey;

		ActionKey ak = method.getAnnotation(ActionKey.class);
		if (ak != null) {
			actionKey = ak.value().trim();
			if ("".equals(actionKey))
				throw new IllegalArgumentException(controllerClass.getName() + "." + methodName
						+ "(): The argument of ActionKey can not be blank.");
			if (!actionKey.startsWith(SLASH))
				actionKey = SLASH + actionKey;
		} else if (methodName.equals("index")) {
			actionKey = controllerKey;
		} else {
			actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName;
		}
		return actionKey;
	}

	private Set<String> buildExcludedMethodName() {
		Set<String> excludedMethodName = new HashSet<String>();
		Method[] methods = Controller.class.getMethods();
		for (Method m : methods) {
			if (m.getParameterTypes().length == 0)
				excludedMethodName.add(m.getName());
		}
		return excludedMethodName;
	}

	public AuthorizeResult invoke(String actionKey) {
		ShiroAuthorizeProcesserInvoker invoker = invokers.get(actionKey);
		if (invoker == null) {
			return AuthorizeResult.ok();
		}

		return invoker.invoke();
	}

	/**
	 * 重新赋值当前用户的权限(在比如:给一个角色临时添加一个权限,需要调用此方法刷新权限,否则还是没有刚赋值的权限)
	 */
	public void reloadAuthorizing() {
		RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager();
		// AccountAuthorizationRealm为在项目中定义的realm类
		AuthorizingRealm shiroRealm = (AuthorizingRealm) rsm.getRealms().iterator().next();
		Subject subject = SecurityUtils.getSubject();
		String realmName = subject.getPrincipals().getRealmNames().iterator().next();
		SimplePrincipalCollection principals = new SimplePrincipalCollection(subject.getPrincipal(), realmName);
		subject.runAs(principals);
		// 用realm删除principle
		shiroRealm.getAuthorizationCache().remove(subject.getPrincipals());
		// 切换身份也就是刷新了
		subject.releaseRunAs();
	}

	/**
	 * 重新赋值权限(在比如:给一个角色临时添加一个权限,需要调用此方法刷新权限,否则还是没有刚赋值的权限)
	 * @param username 用户名
	 */
	public void reloadAuthorizing(String username) {
		RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager();
		// AccountAuthorizationRealm为在项目中定义的realm类
		AuthorizingRealm shiroRealm = (AuthorizingRealm) rsm.getRealms().iterator().next();
		Subject subject = SecurityUtils.getSubject();
		String realmName = subject.getPrincipals().getRealmNames().iterator().next();
		// 第一个参数为用户名,第二个参数为realmName,test想要操作权限的用户
		SimplePrincipalCollection principals = new SimplePrincipalCollection(username, realmName);
		subject.runAs(principals);
		shiroRealm.getAuthorizationCache().remove(subject.getPrincipals());
		subject.releaseRunAs();
	}

	public LambkitShiroConfigService getShiroConfigService() {
		if(shiroConfigService==null) {
			ShiroConfig shiroConfig = Lambkit.config(ShiroConfig.class);
			if (StrKit.notBlank(shiroConfig.getConfigService())) {
				shiroConfigService = Lambkit.get(shiroConfig.getConfigService());
			}
			if(shiroConfigService==null) {
				shiroConfigService = new DefaultLambkitShiroConfigService();
			}
		}
		return shiroConfigService;
	}

	public void setShiroConfigService(LambkitShiroConfigService shiroConfigService) {
		this.shiroConfigService = shiroConfigService;
	}

}
